package org.skydiveapplibrary.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.skydiveapplibrary.data.Database;
import org.skydiveapplibrary.repository.SettingsRepository;

import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;

public final class FileManager
{
	private static final int BUFFER = 2048;
	private static final int FILE_BUFFER = 4096;
	
	private Context mContext;
	private SettingsRepository settingsRepository;
	
	private final String tempFilename = "tempFile.jpg";
	private final String imageFolder = "images";
	
	private final String allImagesQuery = 
			"SELECT distinct Filename FROM log_entry_image " +
			"UNION " +
			"SELECT distinct Filename FROM signature";
	
	public FileManager(Context context)
	{
		mContext = context;
		settingsRepository = new SettingsRepository(context);
	}
	
	public File getAppFile(String filename)
    {
		return new File(mContext.getFilesDir(), filename);
    }
	
	@SuppressLint("WorldReadableFiles")
	private OutputStream getAppFileOutputStream(String filename) throws FileNotFoundException
    {
            FileOutputStream fos = mContext.openFileOutput(filename, Context.MODE_WORLD_READABLE);
            return new BufferedOutputStream(fos);
    }
	
	public Boolean getUseExternalStorage()
	{
		return settingsRepository.getUseExternalStorage();
	}
	
	public File getCacheDir()
	{
		if (isExternalStorageAvailable())
			return mContext.getExternalCacheDir();
		else
			return mContext.getCacheDir();
	}

	public File getCacheFile(String filename)
	{
		return new File(getCacheDir(), filename);
	}
	
	public Writer getCacheFileWriter(String filename) throws IOException
	{
		return new BufferedWriter(new FileWriter(getCacheFile(filename)));
	}
	
	public byte[] getFileContents(File file)
	{
		try
		{
			InputStream is = new BufferedInputStream(new FileInputStream(file));
	
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
	
			while (is.available() > 0)
			{
				bos.write(is.read());
			}
			is.close();
			return bos.toByteArray();
		}
		catch (Exception ex)
		{
			return null;
		}
	}

	public File getSDCardFile(String filename)
	{
		return new File(Environment.getExternalStorageDirectory(), filename);
	}
	
	private File getStorageDir()
	{
		return getStorageDir(getUseExternalStorage());
	}
	
	private File getStorageDir(Boolean useExternalStorage)
	{
		if (useExternalStorage)
			return mContext.getExternalFilesDir(imageFolder);
		else
		{
			String dirPath = mContext.getFilesDir().getAbsolutePath() + File.separator + imageFolder;
			File dir = new File(dirPath);

			if (!dir.exists())
				dir.mkdirs();
			
			return dir;
		}
	}
		
	public File getStorageFile(String filename)
	{
		return getStorageFile(filename, getUseExternalStorage());
	}
		
	public File getStorageFile(String filename, Boolean useExternalStorage)
	{
		File storageDir = getStorageDir(useExternalStorage);
		return new File(storageDir, filename);
	}
	
	public String putStorageFileFromCache(String filename)
	{
		File tempFile = getCacheFile(filename);				
		String md5 = MD5Util.md5(tempFile.getAbsolutePath());
		File destFile = getStorageFile(md5 + ".jpg");
		moveFile(tempFile, destFile);
		return md5;
	}
	
	public void copyFromAppDirToSDCard(String filename, boolean overwrite) throws IOException
	{
		// get source file
		File srcFile = getAppFile(filename);

		// get dest file path
		String destFilePath = Environment.getExternalStorageDirectory() + "/" + srcFile.getName();
		File destFile = new File(destFilePath);

		// if file exists, and not overwrite, return
		if (destFile.exists() && !overwrite)
		{
			return;
		}

		copyFile(srcFile, destFile);
	}
	
	public void deleteCacheFiles(File... exceptFiles)
	{
		File cacheDir = getCacheDir();

		File[] files = cacheDir.listFiles();

		if (files != null)
		{
		    for (File file : files)
		    {				
				// see if it should be deleted
				boolean delete = true;
				for (File exceptFile: exceptFiles)
				{
					if (file.getAbsolutePath().equals(exceptFile.getAbsolutePath()))
					{
						delete = false;
						break;
					}
				}
				
				// delete file
				if (delete)
					file.delete();
		    }
		}
	}
	
	public File writeByteArrayToCacheDir(byte[] bytes)
	{		
		File file = getCacheFile(tempFilename);
		
		writeByteArrayToFile(file, bytes);
		
		return file;
	}
	
	public File writeByteArrayToStorageDir(byte[] bytes)
	{
		File tempFile = getCacheFile(tempFilename);
		writeByteArrayToFile(tempFile, bytes);
		
		String md5 = MD5Util.md5(tempFile.getAbsolutePath());
		File destFile = getStorageFile(md5 + ".jpg");
		moveFile(tempFile, destFile);
		return destFile;
	}
	
	private boolean writeByteArrayToFile(File file, byte[] bytes)
	{
		FileOutputStream fos = null;
		
		try
        {
			fos = new FileOutputStream(file);
			fos.write(bytes);
			fos.flush();
			fos.close();
        }
		catch (Exception ex)
		{
			Log.e("Error occurred parsing image", ex.getMessage());
			return false;
		}
		
		return true;
	}
	
	public File copyToStorageDir(Uri contentUri)
	{
		File cacheFile = copyToCacheDir(contentUri);
		
		String md5 = MD5Util.md5(cacheFile.getAbsolutePath());
		File destFile = getStorageFile(md5 + ".jpg");
		moveFile(cacheFile, destFile);
		return destFile;
	}
	
	private File copyToCacheDir(Uri contentUri)
	{
		File srcFile = new File(getRealPathFromURI(contentUri));
		File destFile = getCacheFile(tempFilename);
		
		copyFile(srcFile, destFile);
		
		if (destFile.length() == 0)
			return null;
		else
			return destFile;
	}

	private String getRealPathFromURI(Uri contentURI) 
	{
		Cursor cursor = mContext.getContentResolver()
	               .query(contentURI, null, null, null, null); 
	    cursor.moveToFirst(); 
	    int idx = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA); 
	    return cursor.getString(idx); 
	}

	public static boolean isExternalStorageAvailable()
	{
		boolean mExternalStorageAvailable = false;
		boolean mExternalStorageWriteable = false;
		String state = Environment.getExternalStorageState();

		if (Environment.MEDIA_MOUNTED.equals(state)) {
		    // We can read and write the media
		    mExternalStorageAvailable = mExternalStorageWriteable = true;
		} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
		    // We can only read the media
		    mExternalStorageAvailable = true;
		    mExternalStorageWriteable = false;
		} else {
		    // Something else is wrong. It may be one of many other states, but all we need
		    //  to know is we can neither read nor write
		    mExternalStorageAvailable = mExternalStorageWriteable = false;
		}
		return mExternalStorageAvailable && mExternalStorageWriteable;
	}
	
	public void deleteUnusedImageFiles()
	{
		File storageDir = getStorageDir();
		List<String> filenames = getAllLogEntryImages();
		
		for (File photo : storageDir.listFiles())
		{
			if (!filenames.contains(photo.getName()))
			{
				photo.delete();
			}
		}
	}
	
	private List<String> getAllLogEntryImages()
    {
		Database database = new Database(mContext);
    	SQLiteDatabase db = database.getReadableDatabase();
    	Cursor cursor = null;
    	List<String> filenames = new ArrayList<String>();
    	
    	try
    	{
	    	cursor = db.rawQuery(allImagesQuery, null);
	
	    	if (cursor.moveToFirst())
	        {                       
	            for (int i = 0; i < cursor.getCount(); i++)
	            {
	            	filenames.add(cursor.getString(0));
	                cursor.moveToNext();
	            }
            }
    	}
    	finally
        {
            // close cursor
            if (cursor != null)
            {
                cursor.close();
            }
            // close db
            db.close();
        }
    	return filenames;
    }
	
	public void zip(String zipFilename, List<File> files) throws IOException
	{
		// streams
		ZipOutputStream zipOutputStream = null;
		InputStream inputStream = null;

		try
		{
			// get zip output stream
			zipOutputStream = new ZipOutputStream(
				new BufferedOutputStream(
					getAppFileOutputStream(zipFilename), BUFFER));

			// for each file
			for (File file : files)
			{
				// create input stream
				inputStream = new BufferedInputStream(new FileInputStream(file), BUFFER);

				// add zip entry
				zipOutputStream.putNextEntry(new ZipEntry(file.getName()));

				// copy data
				copyData(inputStream, zipOutputStream);

				// close input stream
				inputStream.close();
			}
		}
		finally
		{
			// close streams
			if (zipOutputStream != null)
				zipOutputStream.close();
			if (inputStream != null)
				inputStream.close();
		}
	}
	
	public void unzip(File zipFile, File destFolder) throws IOException
	{
		InputStream inputStream = null;
		OutputStream outputStream = null;
		
		try
		{
			// get zip file entries
			ZipFile zf = new ZipFile(zipFile);
			Enumeration<? extends ZipEntry> zipEntries = zf.entries();
			// for each entry
			while (zipEntries.hasMoreElements())
			{
				// get entry
				ZipEntry zipEntry = zipEntries.nextElement();
				
				// get destination file, delete if it exists
				String destFilename = zipEntry.getName();
				File destFile = new File(destFolder, destFilename);

				inputStream = new BufferedInputStream(zf.getInputStream(zipEntry), BUFFER);
				copyToFile(inputStream, destFile);
			}
		}
		finally
		{
			if (inputStream != null)
				inputStream.close();
			if (outputStream != null)
				outputStream.close();
		}
	}
	
	private void copyData(InputStream inputStream, OutputStream outputStream)
		throws IOException
	{
		int count;
        byte data[] = new byte[BUFFER];
        
		// copy data
    	while((count = inputStream.read(data, 0, BUFFER)) != -1)
        {
        	outputStream.write(data, 0, count);
        }
	}
	
	public void moveFile(File srcFile, File destFile)
	{
		copyFile(srcFile, destFile);
		srcFile.delete();
	}
	
	// copy a file from srcFile to destFile, return true if succeed, return
    // false if fail
    public boolean copyFile(File srcFile, File destFile)
    {
        boolean result = false;
        try {
            InputStream in = new FileInputStream(srcFile);
            try {
                result = copyToFile(in, destFile);
            } finally  {
                in.close();
            }
        } catch (IOException e) {
            result = false;
        }
        return result;
    }
    
    /**
     * Copy data from a source stream to destFile.
     * Return true if succeed, return false if failed.
     */
    private static boolean copyToFile(InputStream inputStream, File destFile) {
        try {
            if (destFile.exists())
            {
                destFile.delete();
            }
            FileOutputStream out = new FileOutputStream(destFile);
            try {
                byte[] buffer = new byte[FILE_BUFFER];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) >= 0) {
                    out.write(buffer, 0, bytesRead);
                }
            } finally {
                out.flush();
                try {
                    out.getFD().sync();
                } catch (IOException e) {
                }
                out.close();
            }
            return true;
        } catch (IOException e) {
            return false;
        }
    }
    
    public File writePicasaFileToStorage(Uri uri)
    {
    	File cacheFile = writePicasaFileToCache(uri);
    	
		String md5 = MD5Util.md5(cacheFile.getAbsolutePath());
		File destFile = getStorageFile(md5 + ".jpg");
		moveFile(cacheFile, destFile);
		return destFile;
    }
        
    private File writePicasaFileToCache(Uri uri)
    {
    	File destFile = getCacheFile(tempFilename);

    	try
    	{
    		InputStream is = null;
    		if (uri.toString().contains("android.gallery3d.provider/picasa/")) {
    			is= mContext.getContentResolver().openInputStream(uri);
    		} else {
    			is= new URL(uri.toString()).openStream();
    		}
    		OutputStream os = new FileOutputStream(destFile);
    		copyData(is, os);
    		os.close();
    		
    		if (destFile.length() == 0)
    			return null;
			else
				return destFile;
    		
    	} catch (Exception ex) {
    		Log.d("FileManager", "Exception: " + ex.getMessage());
    		// something went wrong
    		ex.printStackTrace();
    		return null;
    	}
    }
}